package io.noties.markwon.plugin.typogram.bean

/**
 *
 * @author Efstathios Sideris
 */
class CellSet : Iterable<TextGrid.Cell> {
    var internalSet: MutableSet<TextGrid.Cell> = HashSet()
    private var type = TYPE_UNDETERMINED
    private var typeIsValid = false

    constructor() {}
    constructor(other: CellSet) {
        addAll(other)
    }

    override fun iterator(): Iterator<TextGrid.Cell> {
        return internalSet.iterator()
    }

    fun add(cell: TextGrid.Cell?): Any {
        return internalSet.add(cell!!)
    }

    fun addAll(set: CellSet) {
        internalSet.addAll(set.internalSet)
    }

    fun clear() {
        internalSet.clear()
    }

    fun size(): Int {
        return internalSet.size
    }

    //return internalSet.get(0);
    val first: TextGrid.Cell
        get() =//return internalSet.get(0);
            internalSet.iterator().next()

    fun printAsGrid() {
        val grid = TextGrid(maxX + 2, maxY + 2)
        grid.fillCellsWith(this, '*')
        grid.printDebug()
    }

    fun printDebug() {
        val it = iterator()
        while (it.hasNext()) {
            val cell = it.next()
            print(cell)
            if (it.hasNext()) print(" ")
        }
        println()
    }

    val cellsAsString: String
        get() {
            val str = StringBuffer()
            val it = iterator()
            while (it.hasNext()) {
                str.append(it.next().toString())
                if (it.hasNext()) str.append("/")
            }
            return str.toString()
        }

    override fun toString(): String {
        val grid = TextGrid(maxX + 2, maxY + 2)
        grid.fillCellsWith(this, '*')
        return grid.debugString
    }

    /*public BoundarySet(BoundarySet set) {
		Iterator it = set.iterator();
		while(it.hasNext()){
			TextGrid.Cell cell = (TextGrid.Cell) it.next();
			add(new TextGrid.Cell(cell));
		}
	}*/
    fun getType(grid: TextGrid): Int {
        if (typeIsValid) return type
        typeIsValid = true
        if (size() == 1) {
            type = TYPE_OPEN
            return TYPE_OPEN
        }
        val typeTrace = getTypeAccordingToTraceMethod(grid)
        if (DEBUG) {
            println("trace: $typeTrace")
        }
        if (typeTrace == TYPE_OPEN) {
            type = TYPE_OPEN
            return TYPE_OPEN
        }
        if (typeTrace == TYPE_CLOSED) {
            type = TYPE_CLOSED
            return TYPE_CLOSED
        }
        if (typeTrace == TYPE_UNDETERMINED) {
            val typeFill = getTypeAccordingToFillMethod(grid)
            if (typeFill == TYPE_HAS_CLOSED_AREA) {
                type = TYPE_MIXED
                return TYPE_MIXED
            } else if (typeFill == TYPE_OPEN) {
                type = TYPE_OPEN
                return TYPE_OPEN
            }
        }

        //in the case that both return undetermined:
        type = TYPE_UNDETERMINED
        return TYPE_UNDETERMINED
    }

    private fun getTypeAccordingToTraceMethod(grid: TextGrid): Int {
        if (size() < 2) return TYPE_OPEN
        val workGrid = TextGrid.makeSameSizeAs(grid)
        grid.copyCellsTo(this, workGrid)

        //start with a line end if it exists or with a "random" cell if not
        var start: TextGrid.Cell? = null
        for (cell in this) if (workGrid.isLinesEnd(cell)) start = cell
        if (start == null) start = first
        if (DEBUG) println(
            "Tracing:\nStarting at $start (" + grid.getCellTypeAsString(
                start
            ) + ")"
        )
        var previous = start
        var cell: TextGrid.Cell? = null
        var nextCells = workGrid.followCell(previous)
        if (nextCells!!.size() == 0) return TYPE_OPEN
        cell = nextCells.first
        if (DEBUG) println(
            "\tat cell $cell (" + grid.getCellTypeAsString(
                cell
            ) + ")"
        )
        while (cell!! != start) {
            nextCells = workGrid.followCell(cell, previous)
            if (nextCells!!.size() == 0) {
                if (DEBUG) println("-> Found dead-end, shape is open")
                return TYPE_OPEN
            }
            if (nextCells.size() == 1) {
                previous = cell
                cell = nextCells.first
                if (DEBUG) println(
                    "\tat cell $cell (" + grid.getCellTypeAsString(
                        cell
                    ) + ")"
                )
            } else if (nextCells.size() > 1) {
                if (DEBUG) println("-> Found intersection at cell $cell")
                return TYPE_UNDETERMINED
            }
        }
        if (DEBUG) println("-> Arrived back to start, shape is closed")
        return TYPE_CLOSED

//		boolean hasMoved = false;
//
//		CellSet workSet;
//		workSet = new CellSet(this);
//
//		TextGrid.Cell start = (TextGrid.Cell) get(0);
//
//		workSet.remove(start);
//		TextGrid.Cell cell = workSet.findCellNextTo(start);
//
//		while(true && cell != null){
//
//			hasMoved = true;
//			workSet.remove(cell);
//
//			CellSet setOfNeighbours = workSet.findCellsNextTo(cell);
//
//			if(setOfNeighbours.isEmpty()) break;
//
//			TextGrid.Cell c = null;
//			if(setOfNeighbours.size() == 1) c = (TextGrid.Cell) setOfNeighbours.get(0);
//			if(setOfNeighbours.size() > 1) return TYPE_UNDETERMINED;
//			if(c == null) break;
//			else cell = c;
//		}
//		if(cell != null && start.isNextTo(cell) && hasMoved) return TYPE_CLOSED;
//		else return TYPE_OPEN;
    }

    private fun getTypeAccordingToFillMethod(grid: TextGrid): Int {
        if (size() == 0) return TYPE_OPEN
        val tempSet = copyCellSet(this)
        tempSet.translate(-minX + 1, -minY + 1)
        val subGrid = grid.getSubGrid(minX - 1, minY - 1, width + 3, height + 3)
        val abstraction = AbstractionGrid(subGrid, tempSet)
        val temp: TextGrid = abstraction.copyOfInternalBuffer
        val cell1: TextGrid.Cell? = null
        val cell2: TextGrid.Cell? = null
        val width = temp.width
        val height = temp.height
        var fillCell: TextGrid.Cell? = null
        for (y in 0 until height) {
            for (x in 0 until width) {
                val cCell = temp.Cell(x, y)
                if (temp.isBlank(cCell)) {
                    fillCell = cCell
                    break
                }
            }
        }
        if (fillCell == null) {
            System.err.println("Unexpected error: fill method cannot fill anywhere")
            return TYPE_UNDETERMINED
        }
        temp.fillContinuousArea(fillCell, '*')
        if (VERBOSE_DEBUG) {
            println("Buffer after filling:")
            temp.printDebug()
        }
        return if (temp.hasBlankCells()) TYPE_HAS_CLOSED_AREA else TYPE_OPEN
    }

    fun translate(dx: Int, dy: Int) {
        typeIsValid = false
        val it: Iterator<*> = iterator()
        while (it.hasNext()) {
            val cCell = it.next() as TextGrid.Cell
            cCell.x += dx
            cCell.y += dy
        }
    }

    fun find(cell: TextGrid.Cell): TextGrid.Cell? {
        val it = iterator()
        while (it.hasNext()) {
            val cCell = it.next()
            if (cCell == cell) return cCell
        }
        return null
    }

    operator fun contains(cell: TextGrid.Cell): Boolean {
        return  internalSet.contains(cell)
    }

    //	public boolean contains(TextGrid.Cell cell){
    //		Iterator<TextGrid.Cell> it = iterator();
    //		while(it.hasNext()){
    //			TextGrid.Cell cCell = it.next();
    //			if(cCell.equals(cell)) return true;
    //		}
    //		return false;
    //	}
    fun addSet(set: CellSet) {
        typeIsValid = false
        this.addAll(set)
    }

    fun hasCommonCells(otherSet: CellSet): Boolean {
        val it = iterator()
        while (it.hasNext()) {
            val cell = it.next()
            if (otherSet.contains(cell)) return true
        }
        return false
    }

    fun find(x: Int, y: Int): TextGrid.Cell? {
        val it: Iterator<*> = iterator()
        while (it.hasNext()) {
            val cCell = it.next() as TextGrid.Cell
            if (cCell.x == x && cCell.y == y) return cCell
        }
        return null
    }

    fun getFilledEquivalent(textGrid: TextGrid): CellSet? {
        if (getType(textGrid) == TYPE_OPEN) return CellSet(this)
        val grid = TextGrid(maxX + 2, maxY + 2)
        grid.fillCellsWith(this, '*')

        //find a cell that has a blank both on the east and the west
        var cell: TextGrid.Cell? = null
        var finished = false
        var y = 0
        while (y < grid.height && !finished) {
            var x = 0
            while (x < grid.width && !finished) {
                cell = grid.Cell(x, y)
                if (!grid.isBlank(cell)
                    && grid.isBlank(cell.east)
                    && grid.isBlank(cell.west)
                ) {
                    finished = true
                }
                x++
            }
            y++
        }
        if (cell != null) {
            cell = cell.east
            if (grid.isOutOfBounds(cell)) return CellSet(this)
            grid.fillContinuousArea(cell, '*')
            return grid.allNonBlank
        }
        System.err.println("Unexpected error, cannot find the filled equivalent of CellSet")
        return null
    }

    /**
     * Returns the first cell that is found to be next to `cell`.
     *
     * @param cell
     * @return
     */
    fun findCellNextTo(cell: TextGrid.Cell): TextGrid.Cell? {
        val it = iterator()
        while (it.hasNext()) {
            val cCell = it.next()
            if (cCell.isNextTo(cell)) return cCell
        }
        return null
    }

    /**
     * Returns all the cells that are found to be next to `cell`.
     *
     * @param cell
     * @return
     */
    fun findCellsNextTo(cell: TextGrid.Cell): CellSet {
        val set = CellSet()
        val it = iterator()
        while (it.hasNext()) {
            val cCell = it.next()
            if (cCell.isNextTo(cell)) set.add(cCell)
        }
        return set
    }

    fun appendSet(set: CellSet) {
        typeIsValid = false
        val it = set.iterator()
        while (it.hasNext()) {
            val cell = it.next()
            if (find(cell) == null) add(cell)
        }
    }

    fun subtractSet(set: CellSet) {
        typeIsValid = false
        val it= set.iterator()
        while (it.hasNext()) {
            val cell = it.next()
            val thisCell = find(cell)
            thisCell?.let { remove(it) }
        }
    }

    val width: Int
        get() = maxX - minX
    val height: Int
        get() = maxY - minY

    val maxX: Int
        get() {
            var result = 0
            val it= iterator()
            while (it.hasNext()) {
                val cell = it.next()
                if (cell.x > result) result = cell.x
            }
            return result
        }
    val minX: Int
        get() {
            var result = Int.MAX_VALUE
            val it = iterator()
            while (it.hasNext()) {
                val cell = it.next()
                if (cell.x < result) result = cell.x
            }
            return result
        }

    val maxY: Int
        get() {
            var result = 0
            val it= iterator()
            while (it.hasNext()) {
                val cell = it.next()
                if (cell.y > result) result = cell.y
            }
            return result
        }
    val minY: Int
        get() {
            var result = Int.MAX_VALUE
            val it = iterator()
            while (it.hasNext()) {
                val cell = it.next()
                if (cell.y < result) result = cell.y
            }
            return result
        }

    fun remove(cell1: TextGrid.Cell): Any? {
        typeIsValid = false
        val cell = find(cell1)
        return if (cell != null) internalSet.remove(cell) else null
    }

    override fun equals(other: Any?): Boolean {
        val otherSet = other as CellSet?
        return internalSet == otherSet!!.internalSet
    }

    /**
     * Takes into account character info from the grid
     *
     * @return ArrayList of distinct BoundarySetS
     */
    fun breakIntoDistinctBoundaries(grid: TextGrid): ArrayList<CellSet> {
        val result: ArrayList<CellSet>
        val temp = AbstractionGrid(grid, this)
        result = temp.distinctShapes
        return result
    }

    /**
     *
     * @return ArrayList of distinct BoundarySetS
     */
    fun breakIntoDistinctBoundaries(): ArrayList<CellSet> {
        val result: ArrayList<CellSet> = ArrayList<CellSet>()

        //CellSet tempSet = copyCellSet(this);
        //tempSet.translate( - this.getMinX() + 1, - this.getMinY() + 1);

//		TextGrid boundaryGrid = new TextGrid(tempSet.getMaxX()+2, tempSet.getMaxY()+2);
//		boundaryGrid.fillCellsWith(tempSet, '*');
        val boundaryGrid = TextGrid(maxX + 2, maxY + 2)
        boundaryGrid.fillCellsWith(this, '*')
        val it: Iterator<*> = iterator()
        while (it.hasNext()) {
            val cell = it.next() as TextGrid.Cell
            if (boundaryGrid.isBlank(cell.x, cell.y)) continue
            val boundarySet = boundaryGrid.fillContinuousArea(cell.x, cell.y, ' ')
            //boundarySet.translate( this.getMinX() - 1, this.getMinY() - 1);
            result.add(boundarySet)
        }
        return result
    }

    /**
     *
     * Breaks that:
     * <pre>
     * +-----+
     * |     |
     * +  ---+-------------------
     * |     |
     * +-----+
    </pre> *
     *
     * into the following 3:
     *
     * <pre>
     * +-----+
     * |     |
     * +     +
     * |     |
     * +-----+
     *
     * ---
     * -------------------
    </pre> *
     *
     * @param grid
     * @return a list of boundaries that are either open or closed but not mixed
     * and they are equivalent to the `this`
     */
    fun breakTrulyMixedBoundaries(grid: TextGrid): ArrayList<CellSet> {
        val result: ArrayList<CellSet> = ArrayList<CellSet>()
        val visitedEnds = CellSet()
        val workGrid = TextGrid.makeSameSizeAs(grid)
        grid.copyCellsTo(this, workGrid)
        if (DEBUG) {
            println("Breaking truly mixed boundaries below:")
            workGrid.printDebug()
        }
        var it = iterator()
        while (it.hasNext()) {
            val start = it.next()
            if (workGrid.isLinesEnd(start) && !visitedEnds.contains(start)) {
                if (DEBUG) println("Starting new subshape:")
                val set = CellSet()
                set.add(start)
                if (DEBUG) println("Added boundary $start")
                var previous: TextGrid.Cell? = start
                var cell: TextGrid.Cell? = null
                var nextCells = workGrid.followCell(previous!!)
                require(nextCells!!.size() != 0) { "This shape is either open but multipart or has only one cell, and cannot be processed by this method" }
                cell = nextCells.first
                set.add(cell)
                if (DEBUG) println("Added boundary $cell")
                var finished = false
                if (workGrid.isLinesEnd(cell)) {
                    visitedEnds.add(cell)
                    finished = true
                }
                while (!finished) {
                    nextCells = workGrid.followCell(cell!!, previous)
                    if (nextCells!!.size() == 1) {
                        set.add(cell)
                        if (DEBUG) println("Added boundary $cell")
                        previous = cell
                        cell = nextCells.first
                        //if(!cell.equals(start) && grid.isPointCell(cell))
                        //	s.addToPoints(makePointForCell(cell, workGrid, cellWidth, cellHeight, allRound));
                        if (workGrid.isLinesEnd(cell)) {
                            visitedEnds.add(cell)
                            finished = true
                        }
                    } else if (nextCells.size() > 1) {
                        finished = true
                    }
                }
                result.add(set)
            }
        }

        //substract all boundary sets from this CellSet
        val whatsLeft = CellSet(this)

        result.forEach {
            whatsLeft.subtractSet(it)
            if (DEBUG) it.printAsGrid()
        }

        result.add(whatsLeft)
        if (DEBUG) whatsLeft.printAsGrid()
        return result
    }

    fun makeIntoGrid(): TextGrid {
        val grid = TextGrid(maxX + 2, maxY + 2)
        grid.fillCellsWith(this, '*')
        return grid
    }

    fun makeScaledOneThirdEquivalent(): CellSet {
        val gridBig = makeIntoGrid()
        gridBig.fillCellsWith(this, '*')
        if (VERBOSE_DEBUG) {
            println("---> making ScaledOneThirdEquivalent of:")
            gridBig.printDebug()
        }
        val gridSmall = TextGrid((maxX + 2) / 3, (maxY + 2) / 3)
        for (y in 0 until gridBig.height) {
            for (x in 0 until gridBig.width) {
                val cell = gridBig.Cell(x, y)
                if (!gridBig.isBlank(cell)) gridSmall[x / 3, y / 3] = '*'
            }
        }
        if (VERBOSE_DEBUG) {
            println("---> made into grid:")
            gridSmall.printDebug()
        }
        return gridSmall.allNonBlank
    }

    override fun hashCode(): Int {
        var result = internalSet.hashCode()
        result = 31 * result + type
        result = 31 * result + typeIsValid.hashCode()
        return result
    }

    companion object {
        private const val DEBUG = false
        private const val VERBOSE_DEBUG = false
        const val TYPE_CLOSED = 0
        const val TYPE_OPEN = 1
        const val TYPE_MIXED = 2
        const val TYPE_HAS_CLOSED_AREA = 3
        const val TYPE_UNDETERMINED = 4
        private val FAKE = Any()

        /**
         * Deep copy
         *
         * @param set
         * @return
         */
        fun copyCellSet(set: CellSet): CellSet {
            val grid = TextGrid()
            val newSet = CellSet()
            val it: Iterator<*> = set.iterator()
            while (it.hasNext()) {
                val cell = it.next() as TextGrid.Cell
                val newCell = grid.Cell(cell)
                newSet.add(newCell)
            }
            return newSet
        }

        fun removeDuplicateSets(list: ArrayList<CellSet>): ArrayList<CellSet> {
            val uniqueSets: ArrayList<CellSet> = ArrayList<CellSet>()
            val it: Iterator<CellSet> = list.iterator()
            while (it.hasNext()) {
                val set = it.next()
                var isOriginal = true
                val uniquesIt: Iterator<*> = uniqueSets.iterator()
                while (uniquesIt.hasNext()) {
                    val uniqueSet = uniquesIt.next() as CellSet
                    if (set == uniqueSet) {
                        isOriginal = false
                    }
                }
                if (isOriginal) uniqueSets.add(set)
            }
            return uniqueSets
        }
    }
}